﻿using System;
using System.Threading;
using System.Web;
using System.Web.Caching;

namespace Mangox.Common
{
    /// <summary>
    /// 较复杂
    /// </summary>
    public class CacheHelper1
    {
        #region 私有

        /// <summary>
        /// 线程安全锁
        /// </summary>
        private static object asyncLock = new object();

        /// <summary>
        /// 获取状态缓存的键名
        /// </summary>
        /// <param name="cacheName">缓存名称</param>
        /// <returns></returns>
        private static string GetStateCacheKey(string cacheName)
        {
            return string.Format("{0}{1}", cacheName, "_Dep");
        }

        /// <summary>
        /// 设置一个缓存的状态为 Loading 状态，并记录当前进程ID。
        /// <para>一个缓存的 Loading 状态最长可以存在10分钟。</para>
        /// </summary>
        /// <param name="cacheName">缓存名称</param>
        private static void SetCacheLoading(string cacheName)
        {
            HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), Thread.CurrentThread.ManagedThreadId, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero);
        }

        /// <summary>
        /// 是否忽略缓存设置操作。
        /// </summary>
        /// <param name="cacheName">缓存名称</param>
        /// <returns>状态缓存为空或状态缓存值为当前线程ID时，返回 False，代表不忽略缓存设置操作。</returns>
        private static bool IsIgnoreCacheSet(string cacheName)
        {
            if (HttpRuntime.Cache[GetStateCacheKey(cacheName)] == null
                || (int)HttpRuntime.Cache[GetStateCacheKey(cacheName)] == Thread.CurrentThread.ManagedThreadId)
                return false;
            return true;
        }

        #endregion

        #region 设置缓存

        /// <summary>
        /// 设置无过期缓存。
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        public static void SetCache(string cacheName, object val)
        {
            if (val == null)
                return;
            if (IsIgnoreCacheSet(cacheName))
                return;

            HttpRuntime.Cache.Insert(cacheName, val);
            CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
            HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
        }

        /// <summary>
        /// 设置时间过期缓存（滑动过期或绝对过期）。
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        /// <param name="cacheTime">要缓存的时长（分钟）</param>
        /// <param name="isSlidingTime">是否为滑动过期</param>
        public static void SetCache(string cacheName, object val, int cacheTime, bool isSlidingTime)
        {
            if (val == null)
                return;
            if (cacheTime < 1)
                return;
            if (IsIgnoreCacheSet(cacheName))
                return;

            if (isSlidingTime)
                HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime * 2, 0));
            else
                HttpRuntime.Cache.Insert(cacheName, val, null, DateTime.Now.AddMinutes(cacheTime * 2), TimeSpan.Zero);

            CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
            if (isSlidingTime)
                HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime, 0));
            else
                HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, DateTime.Now.AddMinutes(cacheTime), TimeSpan.Zero);
        }

        /// <summary>
        /// 设置文件依赖缓存。
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        /// <param name="file">缓存依赖的文件</param>
        public static void SetCache(string cacheName, object val, string file)
        {
            SetCache(cacheName, val, new string[] { file });
        }

        /// <summary>
        /// 设置文件依赖缓存。
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        /// <param name="files">缓存依赖的文件组</param>
        public static void SetCache(string cacheName, object val, string[] files)
        {
            if (val == null)
                return;
            if (files.Length == 0)
                return;
            if (IsIgnoreCacheSet(cacheName))
                return;

            foreach (string file in files)
                FileHelper.EnsureFile(file);

            HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(12, 0, 0), CacheItemPriority.High, null);
            System.Web.Caching.CacheDependency dependency = new System.Web.Caching.CacheDependency(files, new string[] { cacheName });
            HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
        }

        #endregion

        #region 异步缓存设置（当前线程并非最初检测到缓存过期的线程）

        /// <summary>
        /// 设置无过期缓存。（当前线程并非最初检测到缓存过期的线程）
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        public static void SetCacheAsync(string cacheName, object val)
        {
            if (val == null)
                return;

            HttpRuntime.Cache.Insert(cacheName, val);
            CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
            HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
        }

        /// <summary>
        /// 设置时间过期缓存（滑动过期或绝对过期）。（当前线程并非最初检测到缓存过期的线程）
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        /// <param name="cacheTime">要缓存的时长（分钟）</param>
        /// <param name="isSlidingTime">是否为滑动过期</param>
        public static void SetCacheAsync(string cacheName, object val, int cacheTime, bool isSlidingTime)
        {
            if (val == null)
                return;
            if (cacheTime < 1)
                return;

            if (isSlidingTime)
                HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime * 2, 0));
            else
                HttpRuntime.Cache.Insert(cacheName, val, null, DateTime.Now.AddMinutes(cacheTime * 2), TimeSpan.Zero);

            CacheDependency dependency = new CacheDependency(null, new string[] { cacheName });
            if (isSlidingTime)
                HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, Cache.NoAbsoluteExpiration, new TimeSpan(0, cacheTime, 0));
            else
                HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency, DateTime.Now.AddMinutes(cacheTime), TimeSpan.Zero);
        }

        /// <summary>
        /// 设置文件依赖缓存。（当前线程并非最初检测到缓存过期的线程）
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        /// <param name="file">缓存依赖的文件</param>
        public static void SetCacheAsync(string cacheName, object val, string file)
        {
            SetCacheAsync(cacheName, val, new string[] { file });
        }

        /// <summary>
        /// 设置文件依赖缓存。（当前线程并非最初检测到缓存过期的线程）
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="val">要缓存的对象</param>
        /// <param name="files">缓存依赖的文件组</param>
        public static void SetCacheAsync(string cacheName, object val, string[] files)
        {
            if (val == null)
                return;
            if (files.Length == 0)
                return;

            foreach (string file in files)
                FileHelper.EnsureFile(file);

            HttpRuntime.Cache.Insert(cacheName, val, null, Cache.NoAbsoluteExpiration, new TimeSpan(12, 0, 0), CacheItemPriority.High, null);
            System.Web.Caching.CacheDependency dependency = new System.Web.Caching.CacheDependency(files, new string[] { cacheName });
            HttpRuntime.Cache.Insert(GetStateCacheKey(cacheName), 0, dependency);
        }

        #endregion

        #region 缓存相关方法

        /// <summary>
        /// 删除缓存。
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <param name="isDeleteSrcCache">是否删除源值缓存（False=仅删除状态缓存）</param>
        public static void DelCache(string cacheName, bool isDeleteSrcCache)
        {
            HttpRuntime.Cache.Remove(GetStateCacheKey(cacheName));
            if (isDeleteSrcCache)
                HttpRuntime.Cache.Remove(cacheName);
        }

        /// <summary>
        /// 获取缓存对象。
        /// <para>如果返回的缓存对象不为 Null，则表示缓存可用，</para>
        /// <para>否则，请调用者自数据库或其它位置获取对象，并设置该缓存。</para>
        /// <para></para>
        /// <para>应用实例：</para>
        /// <para>string cacheName = "UserList";</para>
        /// <para>DataTable dtUserList = CacheHelper.GetCache(cacheName) as DataTable;</para>
        /// <para>if (dtUserList == null)</para>
        /// <para>{</para>
        /// <para>    dtUserList = GetUserListByDB();</para>
        /// <para>    // 不会造成循环失效，因为 SetCache 方法仅允许第一个 GetCache 时返回空的进程设置缓存。</para>
        /// <para>    CacheHelper.SetCache(cacheName, dtUserList);</para>
        /// <para>}</para>
        /// <para>return dtUserList;</para>
        /// </summary>
        /// <param name="cacheName">缓存的名称</param>
        /// <returns></returns>
        public static object GetCache(string cacheName)
        {
            if (HttpRuntime.Cache[GetStateCacheKey(cacheName)] == null)
            {
                lock (asyncLock)
                {
                    if (HttpRuntime.Cache[GetStateCacheKey(cacheName)] == null)
                    {
                        // 缓存过期或不存在
                        SetCacheLoading(cacheName);
                        return null;
                    }
                }
            }
            return HttpRuntime.Cache[cacheName];
        }

        #endregion
    }
}
